﻿using System;
using System.CodeDom;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Xml.Linq;
using Microsoft.VisualStudio;

namespace JH.GladeSharpCodeGenerator
{
    [ComVisible(true)]
    public abstract class GeneratorFromGlade : Generator
    {
        private readonly Dictionary<string, string> _eventsMap = new Dictionary<string, string>();
        private void GenerateCodeForSignalElement(XElement signalElement, 
            CodeFieldReferenceExpression widgetField, Type widgetType)
        {
            var signalName = signalElement.Attribute("name").Value;
            var nativeHandlerName = signalElement.Attribute("handler").Value;
            string generatedHandlerName;
            if(!_eventsMap.TryGetValue(nativeHandlerName, out generatedHandlerName))
            {
                generatedHandlerName = AddUniqueMemberId(nativeHandlerName);
                _eventsMap[nativeHandlerName] = generatedHandlerName;
            }

            EventInfo eventInfo;
            if(!Signals[widgetType].TryGetValue(signalName, out eventInfo))
                throw new ArgumentException("Unknown signal : " + signalName);

            InitialisationStatements.Add(
                new CodeAttachEventStatement(
                    widgetField, eventInfo.Name,
                    new CodeDelegateCreateExpression(
                        new CodeTypeReference(eventInfo.EventHandlerType),
                        new CodeThisReferenceExpression(),
                        generatedHandlerName)));
        }

        private void GenerateCodeForWidgetElement(XElement widgetElement)
        {
            var className = widgetElement.Attribute("class").Value;
            var widgetId = widgetElement.Attribute("id").Value;
            if (!Classes.ContainsKey(className))
                throw new ArgumentException("Unknown class : " + className);

            string fieldId = AddUniqueMemberId("_" + widgetId);

            Members.Add(new CodeMemberField
                            {
                                Name = fieldId,
                                Attributes = MemberAttributes.Private,
                                Type = new CodeTypeReference(Classes[className])
                            });

            InitialisationStatements.Add(
                new CodeAssignStatement(
                    new CodeFieldReferenceExpression(new CodeThisReferenceExpression(), fieldId),
                    new CodeCastExpression(
                        Classes[className],
                        new CodeMethodInvokeExpression(
                            new CodeVariableReferenceExpression("gladeXml"),
                            "GetWidget",
                            new CodePrimitiveExpression(widgetId)))));

            foreach (var signalElement in widgetElement.Elements("signal"))
            {
                GenerateCodeForSignalElement(signalElement,
                    new CodeFieldReferenceExpression(new CodeThisReferenceExpression(), fieldId),
                    Classes[className]);
            }

            var childWidgets =
                widgetElement.Elements("child").SelectMany(child => child.Elements("widget"));

            foreach (var childWidget in childWidgets)
                GenerateCodeForWidgetElement(childWidget);
        }

        protected override void GenerateCodeDom(string inputFileContent, string inputFileName)
        {
            // TODO : validate xml before reading it.
            var xdocument = XDocument.Parse(inputFileContent);

            InitialisationStatements.Add(
                new CodeVariableDeclarationStatement(typeof(Glade.XML), "gladeXml",
                    new CodeObjectCreateExpression(typeof(Glade.XML),
                        new CodePrimitiveExpression(FileNamespace + "." +
                            Path.GetFileName(InputFilePath)),
                        new CodePrimitiveExpression(null))));

            foreach (var widgetElmt in xdocument.Element("glade-interface").Elements("widget"))
                GenerateCodeForWidgetElement(widgetElmt);
        }

        public override int DefaultExtension(out string pbstrDefaultExtension)
        {
            pbstrDefaultExtension = ".glade." + CodeProvider.FileExtension.TrimStart(new[] { '.' });
            return VSConstants.S_OK;
        }

        protected const string CustomToolName = "GladeSharpCodeGenerator"; 

    }
}
